package com.googlecode.cswish.struts.hibernate;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.struts.ContextHolder;
import com.jolbox.bonecp.BoneCP;
import com.jolbox.bonecp.BoneCPConfig;
import com.jolbox.bonecp.BoneCPDataSource;
import com.jolbox.bonecp.PoolUtil;

public class MultiSiteDataSource extends BoneCPDataSource {

	private static final Log logger = LogFactory.getLog(MultiSiteDataSource.class);
	
	private Map<String, FinalWrapper> sitePools = new ConcurrentHashMap<String, FinalWrapper>();
	
	@Resource
	private Config config;
	
	@PostConstruct
	private void init() {
		// TODO: use custom BoneCP
	}

	public Connection getConnection() throws SQLException {
		ContextHolder contextHolder = ContextHolder.get();
		String site = contextHolder.getDbName();
		if (site == null || ISite.DEFAULT_NAME.equals(site)) {
			return super.getConnection();
		} else {
			FinalWrapper wrapper = sitePools.get(site);
			if (wrapper == null) {
				synchronized (sitePools) {
					wrapper = sitePools.get(site);
					if (wrapper == null) {
						try {
							if (this.getDriverClass() != null) {
								loadClass(this.getDriverClass());
							}
							
							// TODO: better parameter setting
							BoneCPConfig config = this.clone();
							String url = config.getJdbcUrl();
							int dbBegin = url.lastIndexOf('/');
							int dbEnd = url.lastIndexOf('?');
							String newSiteUrl = url.substring(0, dbBegin + 1) + site + url.substring(dbEnd);
							config.setJdbcUrl(newSiteUrl);
							
							Properties properties = getSiteProperties(site);
							config.setMinConnectionsPerPartition(getProperty(properties, "minConnectionsPerPartition", 0));
							config.setMaxConnectionsPerPartition(getProperty(properties, "maxConnectionsPerPartition", 10));
							config.setPartitionCount(getProperty(properties, "partitionCount", 1));
							config.setAcquireIncrement(getProperty(properties, "acquireIncrement", 2));
							config.setStatementsCacheSize(getProperty(properties, "statementsCacheSize", 20));
							
							// FIXME: disable the watch thread
							//config.setIdleConnectionTestPeriodInSeconds(0);
							//config.setIdleMaxAgeInSeconds(0);
							//config.setDisableConnectionTracking(true);
							
							wrapper = new FinalWrapper(new BoneCP(config));
							sitePools.put(site, wrapper);
						} catch (Exception e) {
							throw new SQLException(PoolUtil.stringifyException(e));
						}
					}
				}
			}
			return wrapper.value.getConnection();
		}
	}
	
	private int getProperty(Properties properties, String propertyName, int defaultVal) {
		String propertyVal = properties.getProperty("datasource." + propertyName);
		if (propertyVal != null) {
			return Integer.parseInt(propertyVal);
		} else {
			return defaultVal;
		}
	}
	
	private Properties getSiteProperties(String site) {
		Properties properties = new Properties();
		String siteCfg = config.getPluginDir(site) + "website.properties";
		File siteCfgFile = new File(siteCfg);
		if (siteCfgFile.exists()) {
			try {
				properties.load(new FileInputStream(siteCfgFile));
			} catch (IOException ex) {
				logger.error("Failed to load site properties", ex);
			}
		}
		return properties;
	}
	
	public void shutdown(String site) {
		FinalWrapper wrapper = sitePools.get(site);
		if (wrapper != null) {
			wrapper.value.shutdown();
			sitePools.remove(site);
		}
	}
	
	class FinalWrapper {
		private final BoneCP value;

		public FinalWrapper(BoneCP value) {
			this.value = value;
		}
	}
}
